#![allow(dead_code)]
use gset::Getset;
use pyo3::ffi::c_str;
use pyo3::prelude::*;
use std::ffi::CString;
use std::io::Read;
use std::ops::Deref;
use std::str::FromStr;
#[allow(dead_code)]
enum FileType {
    CSV,
    PNG,
}
impl Deref for FileType {
    type Target = str;

    fn deref(&self) -> &Self::Target {
        match self {
            FileType::CSV => "csv",
            FileType::PNG => "png",
        }
    }
}
trait PredictModel {
    fn get_model_type() -> anyhow::Result<String>;
}
pub trait Predict {
    fn pyo3_predict(&self, params: PredictParams) -> anyhow::Result<String>;
}
#[allow(dead_code)]
pub struct SEIR;
#[allow(dead_code)]
pub struct SIR;
impl PredictModel for SEIR {
    fn get_model_type() -> anyhow::Result<String> {
        Ok(String::from_str("seir")?)
    }
}
impl PredictModel for SIR {
    fn get_model_type() -> anyhow::Result<String> {
        Ok(String::from_str("sir")?)
    }
}
#[derive(Debug, Getset)]
pub struct ParamsBuilder {
    #[getset(set_own, name = "with_filename", vis = "pub")]
    #[getset(get, vis = "pub")]
    file_name: String,
    #[getset(set_own, name = "with_filetype", vis = "pub")]
    #[getset(get, vis = "pub")]
    file_type: String,
    #[getset(set_own, name = "with_username", vis = "pub")]
    #[getset(get, vis = "pub")]
    username: String,
    //TODO: 在此添加新的内容
}
impl Default for ParamsBuilder {
    fn default() -> Self {
        Self {
            file_name: Default::default(),
            file_type: "text/csv".to_string(),
            username: Default::default(),
        }
    }
}
impl ParamsBuilder {
    #[inline]
    pub fn builder() -> ParamsBuilder {
        Default::default()
    }
    #[inline]
    pub fn build(self) -> PredictParams {
        PredictParams::default()
            .filename(self.file_name().to_owned())
            .filetype(self.file_type().to_owned())
            .username(self.username().to_owned())
    }
}
//TODO: 待重构
#[derive(Debug, Clone, Getset, Default)]
#[allow(dead_code)]
pub struct PredictParams {
    #[getset(get, name = "get_filename", vis = "pub")]
    #[getset(set_own, name = "filename", vis = "pub")]
    file_name: String,
    #[getset(get, name = "get_username", vis = "pub")]
    #[getset(set_own, name = "username", vis = "pub")]
    user_name: String,
    #[getset(get, name = "get_filetype", vis = "pub")]
    #[getset(set_own, name = "filetype", vis = "pub")]
    file_type: String,
}
impl PredictParams {}
#[derive(Debug, Getset)]
pub struct Learner<Model = SEIR> {
    _marker: std::marker::PhantomData<Model>,
}
impl Default for Learner {
    #[inline]
    fn default() -> Self {
        Self {
            _marker: std::marker::PhantomData::<SEIR>,
        }
    }
}
impl Learner {
    #[inline]
    pub fn new() -> Self {
        Default::default()
    }
    #[inline]
    pub fn use_sir(self) -> Learner<SIR> {
        Learner {
            _marker: std::marker::PhantomData::<SIR>,
        }
    }
}

impl Predict for Learner<SEIR> {
    #[inline]
    fn pyo3_predict(&self, params: PredictParams) -> anyhow::Result<String> {
        predict::<SEIR>(params)
    }
}
impl Predict for Learner<SIR> {
    #[inline]
    fn pyo3_predict(&self, params: PredictParams) -> anyhow::Result<String> {
        predict::<SIR>(params)
    }
}
#[cfg(Py_3_11)]
#[inline]
fn predict<Model>(params: PredictParams) -> anyhow::Result<String>
where
    Model: PredictModel,
{
    pyo3::prepare_freethreaded_python();
    let mut python_file = String::new();
    #[cfg(target_os = "linux")]
    let mut file = std::fs::File::open(format!("/app/model/{}.py", Model::get_model_type()?))
        .map_err(|e| {
            let message = format!("{}:{}.py", e.to_string(), Model::get_model_type().unwrap());
            log::error!("{}", message);
            e
        })?;

    #[cfg(target_os = "windows")]
    let mut file = std::fs::File::open(format!("src/{}_windows.py", Model::get_model_type()?))
        .map_err(|e| {
            let message = format!(
                "{}:{}_windows.py",
                e.to_string(),
                Model::get_model_type().unwrap()
            );
            log::error!("{}", message);
            e
        })?;
    file.read_to_string(&mut python_file)?;
    let python_code = CString::new(python_file)?;
    let file_name = CString::new(format!("{}.py", Model::get_model_type()?))?;
    let module_name = CString::new(format!("{}", Model::get_model_type()?))?;
    Python::with_gil(move |py: Python<'_>| {
        //导入模块
        #[cfg(target_os = "linux")]
        let _ = PyModule::from_code(
            py,
            c_str!(include_str!("/app/python_moudle/exist_dir.py")),
            c_str!("exist_dir.py"),
            c_str!("exist_dir"),
        )?;
        #[cfg(target_os = "windows")]
        let _ = PyModule::from_code(
            py,
            c_str!(include_str!("exist_dir.py")),
            c_str!("exist_dir.py"),
            c_str!("exist_dir"),
        )
        .map_err(|e| {
            log::error!("{}", e.to_string());
            e
        })?;
        let activators = PyModule::from_code(py, &python_code, &file_name, &module_name)?;
        let path: String = activators
            .getattr("main")?
            .call1((
                params.get_filename(),
                params.get_username(),
                params.get_filetype(),
            ))?
            .extract()?;
        println!("result_path:{}", path);
        Ok(path)
    })
}

#[cfg(Py_3_11)]
#[cfg(target_os = "linux")]
pub fn test_docker_invoke() -> anyhow::Result<()> {
    pyo3::prepare_freethreaded_python();
    Python::with_gil(|py| {
        let moudle = PyModule::from_code(
            py,
            c_str!(include_str!("/app/model/test.py")),
            c_str!("test.py"),
            c_str!("test"),
        )?;
        let res: Vec<i32> = moudle.getattr("main")?.call0()?.extract()?;
        log::info!("{:#?}", res);
        Ok(())
    })
}

#[cfg(target_os = "windows")]
pub fn test_docker_invoke_windows() -> anyhow::Result<()> {
    pyo3::prepare_freethreaded_python();
    Python::with_gil(|py| {
        PyModule::import(py, "numpy")?;
        let moudle = PyModule::from_code(
            py,
            c_str!(include_str!("test.py")),
            c_str!("test.py"),
            c_str!("test"),
        )?;
        let res: Vec<i32> = moudle.getattr("main")?.call0()?.extract()?;
        log::info!("{:#?}", res);
        Ok(())
    })
}
